import json
import logging
import os
import subprocess
import time
import shutil
import traceback

import numpy as np
import datetime
from threading import Thread
import random
import pandas as pd
from django.shortcuts import HttpResponse
from dwebsocket import require_websocket, accept_websocket
from django.db.models import Q
from api import models, utils
from api.pojie_A10.producer_actual_values_yqd_A10 import SampleProducer_actual_values_yqd_A10
from api.pojie_A10.producer_actual_values_A10 import SampleProducer_actual_values_A10
from api.pojie_A10.producer_actual_values_yqd_A10 import SampleProducer_actual_values_yqd_A10
from api.webRequest.feedback import feedback_to_update_threshold
from api.webRequest import assign
from api.serial import mould
from api.serial.data import SocketResponse
from api.serial.socket_api import WebSocketConnection
from api.service import exit_prod_service
from detecter import settings
from api.serial.detect import detect_sample
from detecter.settings import Collection_frequency

logger = logging.getLogger('django')


# 启动数据采集和样本检测
# @accept_websocket  # 只有加了这个装饰器，这个视图函数才能处理websocket请求
# 启动数据采集和样本检测
@require_websocket
def websocket_handler(request, mould_code):
    # 高斯分布的sigma倍数
    rate_threshold = request.GET.get('rate', models.Mould.default_gauss_times)
    # 封装一下websocket
    websocket = WebSocketConnection(request.websocket)  # 获取request里面传送过来的websocket对象，进行一个自己的封装，方便使用
    if request.is_websocket():  # 如果是一个websocket请求
        # 获取当前模具的实例
        current_mould = mould.CurrentMould.get_instance()

        # 1、如果实例为空说明程序第一次启动，如果is_order_running是false说明前端点击了停止生产的按钮
        # 2、2023-11-15：producer存活的情况下，启动consumer
        if (current_mould is None) or (not current_mould.is_order_running):
            # 如果是第一次启动，则创建实例对象
            if current_mould is None:
                # 根据模具编号和websocket连接 创建实例
                current_mould = mould.CurrentMould.new_instance(mould_code, websocket)
                websocket.current_order = current_mould
                try:
                    # 关闭预启动线程
                    actual_values_yqd_A10 = SampleProducer_actual_values_yqd_A10.get_instance()
                    actual_values_yqd_A10.stop()
                    time.sleep(10)
                except:
                    logger.warning(traceback.format_exc())
                    pass

                # 初始化和启动   只有初始化和启动以后，这个模具实例里面才会有生产者和消费者对象
                current_mould.begin_or_continue_order(rate_threshold)  # 获取模具对象并且启动各种线程
                logger.warning(f'websocket 连接,工单号:{current_mould.mould_code}')

            # 如果只是为了再次启动consumer线程
            if current_mould is not None and not current_mould.is_order_running:
                current_mould.mould_code=mould_code
                current_mould.websocket = websocket
                current_mould.is_order_running = True
                current_mould.is_socket_connected = True
                websocket.current_order = current_mould
                current_mould.consumer.running.set()  # 将消费者线程里面的标志设为True
                logger.warning(f'websocket 连接,工单号:{current_mould.mould_code}')

            # 这个线程主要维持着与前端的websocket连接，样本检测线程是consumer, 数据采集线程是producer
            # 这样做的好处是一旦网络原因或者人为把浏览器关闭后还可以续连
            # 但是time.sleep的做法并不妥当
            while current_mould.is_socket_connected:
                time.sleep(3)
            logger.warning(f'websocket 断开,工单号:{current_mould.mould_code}')
            utils.R.ok(f'websocket 断开,工单号:{current_mould.mould_code}')

        # 在producer和consumer线程都存活时，关闭浏览器后重新打开浏览器  （关闭浏览器时会导致websocket断开连接）
        elif current_mould.is_order_running:  # 应注意在consumer仍然alive情况下websocket重连的问题, 注意是current_order的websocket
            # current_order is running
            current_mould_code = current_mould.mould_code
            if mould_code == current_mould_code:
                # 断线重连
                current_mould.websocket = websocket
                current_mould.websocket.current_order = current_mould
                current_mould.is_socket_connected = True
                current_mould.websocket.msg_send(SocketResponse.error(f'机台模具 {current_mould.mould_code} 正在工作中, 续连成功'))
                while current_mould.is_socket_connected:
                    time.sleep(3)
                logger.warning(f'浏览器与服务器连接断开,工单号:{current_mould.mould_code}')
            else:
                # request.websocket.send(SocketResponse.error(f'机台模具 {current_mould.mould_code} 尚未完成,如要停止运行,请点击停止按钮'))
                # 手动改自动
                # 先停止生产
                if current_mould:
                    if current_mould.is_order_running:
                        exit_prod_service(current_mould)
                        return utils.R.ok(f'工单号:{current_mould.mould_code}停止检测')
                    else:
                        msg = f'后台工单不在运行,{current_mould.mould_code}'
                else:
                    msg = 'current_order is null'
                logger.warning(msg)

                # 根据模具编号和websocket连接 创建实例
                current_mould = mould.CurrentMould.new_instance(mould_code, websocket)  # 初始化所有，此时current_mould为空
                websocket.current_order = current_mould
                # 初始化和启动
                current_mould.begin_or_continue_order(rate_threshold)
                if not current_mould.consumer.is_consumer_running:
                    try:
                        current_mould.consumer.begin()
                    except:
                        pass
                logger.warning(f'websocket 连接,工单号:{current_mould.mould_code}')
                # 这个线程主要维持着与前端的websocket连接，样本检测线程是consumer, 数据采集线程是producer
                # 这样做的好处是一旦网络原因或者人为把浏览器关闭后还可以续连
                # 但是time.sleep的做法并不妥当
                # thread = Thread(target=accept, args=(current_mould,))
                # thread.start()
                while current_mould.is_socket_connected:
                    time.sleep(3)
                logger.warning(f'websocket 断开,工单号:{current_mould.mould_code}')
                utils.R.ok(f'websocket 断开,工单号:{current_mould.mould_code}')
    else:
        return utils.R.error()


# 停止或退出 current_order
def stop_or_exit_current_order(request):
    if request.method == 'GET':
        current_mould = mould.CurrentMould.get_instance()
        if current_mould:
            if current_mould.is_order_running:
                exit_prod_service(current_mould)  # 停止消费者
                return utils.R.ok(f'工单号:{current_mould.mould_code}停止检测')
            else:
                msg = f'后台工单不在运行,{current_mould.mould_code}'
        else:
            msg = 'current_order is null'
    else:
        msg = 'only support GET method'
    return utils.R.error(msg)


def get_latest_var():
    var_list = list(utils.var_queue)
    return utils.R.ok(json.dumps(var_list))


# def accept(current_mould):
#     while current_mould.is_order_running:
#         portrait_result = current_mould.portrait_result
#         sample_list = current_mould.producer.get_sample_list()
#         while len(sample_list) > 0:
#             current_sample = sample_list.pop(0)
#             sample_id = current_sample.id
#             try:
#                 shouji_chuanshu(sample_id, current_sample, current_mould, portrait_result, detect_sample)
#                 # print('数据传输到mysql')
#             except:
#                 print('数据没有传输到mysql')


def modify_rate(request, mould_code):
    current_mould = mould.CurrentMould.get_instance()
    if current_mould and current_mould.prod_mould:
        db_mould = current_mould.prod_mould
    else:
        db_mould = models.Mould.objects.get(mould_code=mould_code)
    rate = request.GET.get('rate', models.Mould.default_gauss_times)
    rate = float(rate)
    if 0.0 < rate < 100.0:
        db_mould.rate_threshold = rate
        db_mould.save(force_update=True)
        return utils.R.ok()
    else:
        return utils.R.error(f'异常率 {rate} 不符合要求')


def unsigned_samples(request, mould_code):
    samples = models.Sample.objects.filter(mould_code=mould_code, label=models.Label.get_default_label()) \
        .values('made_no', 'sample_id', 'prod_time', 'label_id', 'label__label_name', 'reason_id',
                'reason__reason_name')
    # 转list_dic
    samples = list(samples)
    # 新增字段state_name
    for sample in samples:
        sample['state_name'] = models.Sample.state_choice[sample['state']][1]
        sample['prod_time'] = sample['prod_time'].strftime("%m-%d %H:%M:%S")
    return utils.R.ok(samples)


# get latest 50 products
def all_normal_samples(request, mould_code):
    try:
        db_mould = models.Mould.objects.get(mould_code=mould_code)
    except:
        return utils.R.error("该模具不存在")
    samples = models.Sample.objects.filter(mould=db_mould).order_by('-id').all()[:50]
    samples = samples.values('mould__mould_code', 'state', 'sample_id', 'prod_time', 'label_id', 'label__label_name',
                             'reason_id',
                             'reason__reason_name', 'times')
    samples = list(samples)
    if samples:
        # 新增字段state_name
        for sample in samples:
            sample['state_name'] = models.Sample.state_choice[sample['state']][1]
            sample['prod_time'] = sample['prod_time'].strftime("%m-%d %H:%M:%S")
    return utils.R.ok(samples)


# get latest 50 negative products
def all_abnormal_samples(request, mould_code):
    try:
        db_mould = models.Mould.objects.get(mould_code=mould_code)
    except:
        return utils.R.error("该模具不存在")
    samples = models.Sample.objects.filter(Q(mould=db_mould, state=0) | Q(mould=db_mould, state=3)).order_by(
        '-id').all()[:50]
    samples = samples.values('mould__mould_code', 'state', 'sample_id', 'prod_time', 'label_id', 'label__label_name',
                             'reason_id',
                             'reason__reason_name', 'times')
    samples = list(samples)
    if samples:
        # 新增字段state_name
        for sample in samples:
            sample['state_name'] = models.Sample.state_choice[sample['state']][1]
            sample['prod_time'] = sample['prod_time'].strftime("%m-%d %H:%M:%S")
    return utils.R.ok(samples)


def all_labels(request):
    labels = models.Label.objects.all().values()
    return utils.R.ok(list(labels))


def all_reasons(request):
    reasons = models.Reason.objects.all().values()
    return utils.R.ok(list(reasons))


def insert_reason(request):
    label_name = request.POST.get('reason_name')
    label_desc = request.POST.get('reason_desc')
    new_label = models.Label()
    new_label.label_name = label_name
    new_label.label_desc = label_desc
    new_label.save()
    return utils.R.ok()


def delete_reason(request, reason_id):
    if reason_id != models.Reason.default_id and reason_id != models.Reason.good_id:
        try:
            label = models.Reason.objects.get(pk=reason_id)
        except:
            return utils.R.error("指定原因不存在")
        label.delete()
        return utils.R.ok()
    else:
        return utils.R.error("指定原因不允许删除")


def insert_label(request):
    label_name = request.POST.get('label_name')
    label_desc = request.POST.get('label_desc')
    new_label = models.Label.objects.create(label_name=label_name, label_desc=label_desc)
    db_moulds = models.Mould.objects.all()
    for db_mould in db_moulds:
        models.NegFigure.objects.create(mould=db_mould, label=new_label)
    return utils.R.ok()


def delete_label(request, label_id):
    if label_id != models.Label.default_id and label_id != models.Label.good_id:
        try:
            label = models.Label.objects.get(id=label_id)
        except:
            return utils.R.error("指定标签不存在")
        label.delete()
        return utils.R.ok()
    else:
        return utils.R.error("指定标签不允许删除")


def index(request):
    return HttpResponse("Hello World")


from django.template import loader, RequestContext


def index1(request):
    # return HttpResponse("老铁，没毛病！")
    # 1.加载模板文件，模板对象
    temp = loader.get_template('detecter/index.html')
    # 2.定义模板上下文：给模板文件传递数据
    # context = RequestContext(request, {})
    context = ({})
    # 3.模板渲染：产生标准的html内容
    res_html = temp.render(context)
    # 4.返回给浏览器
    return HttpResponse(res_html)


# 重新建模，数据库內删掉模具号的所有sample，figure以及负样本相关的表，清空所有相关的文件
# by zxy on 20200103
def rebuild(request, mould_code):
    try:
        # prod_mould = models.Mould.objects.get(mould_code=mould_code)
        current_mould = mould.CurrentMould.get_instance()
        prod_mould = current_mould.prod_mould
    except:
        return utils.R.error(f'未找到编号为{mould_code}的模具')
    mould_id = prod_mould.id
    prod_mould.sample_id = 0
    prod_mould.pos_figure = ""
    prod_mould.pos_count = 0
    prod_mould.neg_count = 0
    prod_mould.threshold = settings.DEFAULT_THRESHOLD
    prod_mould.save(force_update=True)

    sample_list = models.Sample.objects.filter(mould_id=mould_id)
    sample_list.delete()

    neg_sample_list = models.NegFigure.objects.filter(mould_id=mould_id)
    neg_sample_list.delete()

    # 保存负样本信息，方便回溯
    now = datetime.datetime.now()
    history_path = os.path.join(settings.BASE_DIR, 'data', '历史信息', str(mould_code) + '__' + now.strftime("%Y-%m-%d"))
    utils.check_dir_exist(history_path)
    negative_file_path = os.path.join(settings.BASE_DIR, 'data', 'sample', mould_code, "负样本信息")
    if os.path.exists(negative_file_path):
        shutil.copytree(negative_file_path, history_path)
    # 清空文件
    file_path = os.path.join(settings.BASE_DIR, 'data', 'sample', str(mould_code))
    try:
        if os.path.exists(file_path):
            shutil.rmtree(file_path)
    except Exception as e:
        logger.warning('临时更改机台号所导致：{}'.format(e))
    logger.info(f'编号为{mould_code}的模具可以重新建模了')
    return utils.R.ok(f'编号为{mould_code}的模具可以重新建模了')


# zxy 修改于20200104
def feedback(request, mould_code, sample_id, sample_type):
    current_mould = mould.CurrentMould.get_instance()  # 获取模具实例对象
    db_mould = models.Mould.objects.filter(mould_code=mould_code)[0]  # 根据模具编号从数据库中获取模具对象
    if not db_mould:
        return utils.R.error("未找到有效的模具记录")
    is_current_order_not_exist = False
    if not current_mould:  # 如果当前模具实例并不存在
        is_current_order_not_exist = True
        logger.info('current_order is null')
        current_mould = mould.CurrentMould.new_instance(mould_code, None)  # 创建一个模具实例
    current_mould.prod_mould = db_mould
    logger.info("flag feedback")
    result = feedback_to_update_threshold(db_mould, current_mould, sample_id, sample_type)
    if is_current_order_not_exist:
        current_mould.set_instance(None)  # 设置mould.CurrentMould的实例
    return result


# # zxy 修改于20200110
# def cluster_handler(request, mould_code, mode, data):
#     db_mould = models.Mould.objects.filter(mould_code=mould_code)[0]
#     if not db_mould:
#         return R.error("未找到有效的模具记录")
#     result = assign_cluster(db_mould, mode, data)
#     return result

# zxy 修改于20200110 POST
def cluster_handler(request):
    # 获取非表单数据
    post_body = request.body
    json_result = json.loads(post_body)
    # 获取模具编号
    mould_code = json_result['mould_code']
    # mould_code = "2115D6085"
    mode = json_result["mode"]
    data = json_result["data"]
    logger.info(str(mould_code))
    try:
        db_mould = models.Mould.objects.filter(mould_code=mould_code)[0]
    except:
        return utils.R.error("未找到模具")
    if not db_mould:
        return utils.R.error("未找到有效的模具记录")
    try:
        t = assign.AssignThread(db_mould, mode, data)
        t.start()
        result = t.get_result()
        del t
        return result
    except:
        return utils.R.error("部分样本数据未检测，负样本聚类失败")


# zxy 修改于20200111 POST
def sample_handler(request):
    post_body = request.body
    json_result = json.loads(post_body)
    mould_code = json_result['mould_code']
    mode = json_result["mode"]
    data = json_result["data"]
    logger.info(str(mould_code))
    try:
        db_mould = models.Mould.objects.filter(mould_code=mould_code)[0]
    except:
        return utils.R.error("未找到模具")
    if not db_mould:
        return utils.R.error("未找到有效的模具记录")
    result = assign.assign_sample(db_mould, mode, data)
    return result


# zxy 修改于 20200610 GET
# 返回历时生成某样本(默认为正样本)的图像
def sample_image(request, mould_code, sample_id):
    mould_code = mould_code.replace(' ', '')
    accepted_file = os.path.join(settings.BASE_DIR, 'data', 'sample', mould_code, 'accepted.csv')
    rejects_file = os.path.join(settings.BASE_DIR, 'data', 'sample', mould_code, 'rejects.csv')
    undetected_file = os.path.join(settings.BASE_DIR, 'data', 'sample', mould_code, 'temp', 'undetected.csv')
    one_hour_temp_file = os.path.join(settings.BASE_DIR, 'data', 'sample', mould_code, 'temp', 'one_hour_temp.csv')
    sample = None
    period_len_list = None
    if (not os.path.exists(accepted_file)) and (not os.path.exists(rejects_file)):
        logger.info("路径1：{}".format(accepted_file))
        logger.info("路径2：{}".format(rejects_file))
        return utils.R.error("路径不存在，请检查路径是否完整")
    # 存在于历史文件中的样本，无论好坏样本
    if os.path.exists(one_hour_temp_file):
        sample, period_len_list = utils.get_sample(one_hour_temp_file, sample_id)
    # 好样本
    if os.path.exists(accepted_file):
        sample, period_len_list = utils.get_sample(accepted_file, sample_id)

    if not sample and os.path.exists(rejects_file):
        sample, period_len_list = utils.get_sample(rejects_file, sample_id)
    if not sample and os.path.exists(undetected_file):
        return utils.R.error("该样本出现分段不匹配或其他不进入系统分析的错误，故不显示图像")

    if not sample:
        return utils.R.error("该样本暂时未加载，请跳过最新的2个样本")
        # return utils.R.error("该样本未检测")

    try:
        N = np.min(period_len_list)
        sample_index = np.array(list(range(0, N, Collection_frequency)))
        ch1_arr = np.zeros(N)
        ch2_arr = np.zeros(N)
        speed_arr = np.zeros(N)
        info = sample.get_userful_info_dic()
        ch1_arr += np.array(info['actual_pressure'])[sample_index]
        ch2_arr += np.array(info['actual_location'])[sample_index]
        speed_arr += np.array(info['actual_speed'])[sample_index]
        # 封装返回数据
        ch1_arr_test = np.array(ch1_arr)
        ch1_arr_test = np.maximum(ch1_arr_test, 0)
        sample_result = {
            "sample_id": sample_id,
            # "sample_pressure": ch1_arr.tolist(),
            # "sample_position": ch2_arr.tolist(),
            # "sample_speed": speed_arr.tolist(),
            "sample_pressure": [round(i, 2) for i in ch1_arr_test],
            "sample_position": [round(i, 2) for i in np.array(ch2_arr)],
            "sample_speed": [round(i, 2) for i in np.array(speed_arr)],
        }
        result = {}
        result.update({"sample_result": sample_result})
        return utils.R.ok(json.dumps(result))
    except:
        return utils.R.error("压力或螺杆位置或速度帧数据未采集到，图像绘制失败")


def Send_MACHINE_CODE(request):
    MACHINE_CODE = settings.MACHINE_CODE
    try:
        sampleproducer_actual_values = SampleProducer_actual_values_A10.get_instance()
        machine_status = sampleproducer_actual_values.machine_status
    except:
        machine_status = ""
    res_json = {"code": MACHINE_CODE, "machine_status": machine_status}
    return HttpResponse(json.dumps(res_json))


# def refresh_google():
#     print('---------------------------------------------------------------------------------------------重新开启刷新浏览器的进程')
#     subprocess.Popen(["/usr/bin/python3", '/home/whhd/refresh_google.py'])  # 开启一个新的进程进行重启
#
#
# refresh_google()  # 重新打开google浏览器
